﻿using System.Collections.Generic;
using Microsoft.Xna.Framework;
using RainEngine.SceneNodes;
using RainEngine.Rendering;
using RainEngine.SceneNodes.UI;

namespace RainEngine.SceneManagement
{
    /// <summary>
    /// 处理的节点的剔除。
    /// 剔除包含两个过程：更新过程中的剔除和绘制过程中的剔除。
    /// </summary>
    internal static class CullManager
    {
        /// <summary>
        /// 创建一个需要更新的节点集合，在Update过程中被调用。 
        /// 此过程更新FrameBits中的UpdatePhaseFrustumChecked和UpdatePhaseFrustumDisjoint，在Draw过程中的剔除依赖于这些值。             
        /// </summary>
        /// <param name="updateList">经过剔除的节点索引集合</param>
        /// <param name="activeNodeList">要更新的节点集合</param>
        /// <param name="cullInfo">剔除信息</param>
        /// <param name="currentStat">当前节点的统计信息</param>
        public static void UpdatePhaseCulling(IndexList updateList, List<SceneNode> activeNodeList, CullingInfo cullInfo, ref RenderStatistics currentStat)
        {
            //将updateList归零
            updateList.Reset();

            //遍历activeNodeList集合
            for (int i = 0; i < activeNodeList.Count; i++)
            {
                // UI节点不需要进行剔除
                if (activeNodeList[i] is UISceneNode)
                    continue;
                
                Renderable3DSceneNode node = activeNodeList[i] as Renderable3DSceneNode;
                //---------------------------------------------------------------------
                //  判断当前节点是否需要进行剔除??
                //  要剔除的节点的Enabled位必须设置为1而DisableUpdateCulling位设置为0!!!
                //---------------------------------------------------------------------
                if (((byte)node.frameBits & ((byte)StatusBits.Enabled | (byte)StatusBits.DisableUpdateCulling)) == (byte)StatusBits.Enabled)
                {
                    currentStat.nodesEnabled++;

                    //------------------------------------
                    //  如果需要进行剔除
                    //  则将UpdatePhaseFrustumChecked位设置为1
                    //------------------------------------
                    node.frameBits |= StatusBits.UpdatePhaseFrustumChecked;                              

                    //  现在进行剔除检测
                    //  如果节点在视锥体内，则将节点的UpdatePhaseFrustumDisjoint位设置为0，并将此节点添加到updateList集合中               
                    if (cullInfo.BoundingFrustum.Contains(node.Pose.aaBB) != ContainmentType.Disjoint)
                    {
                        node.frameBits &= ~StatusBits.UpdatePhaseFrustumDisjoint;
                        updateList.AddIndex(i);                        
                    }
                    //如果节点不在视锥体内，则将UpdatePhaseFrustumDisjoint位设置为1
                    else
                    {
                        node.frameBits |= StatusBits.UpdatePhaseFrustumDisjoint;
                    }
                }
                else
                {
                    //---------------------------------------
                    //  如果不需要进行剔除                
                    //  则将UpdatePhaseFrustumChecked位设置为0 
                    //  将UpdatePhaseFrustumDisjoint设置为0
                    //---------------------------------------
                    node.frameBits &= ~( StatusBits.UpdatePhaseFrustumChecked | StatusBits.UpdatePhaseFrustumDisjoint);
                    
                    if (((byte)node.frameBits & (byte)StatusBits.Enabled) == (byte)StatusBits.Enabled)
                    {
                        updateList.AddIndex(i);
                        currentStat.nodesEnabled++;
                    }
                }
            }
        }
        /// <summary>
        /// 创建一个需要被绘制的节点集合。在Draw过程中被调用。
        /// </summary>
        /// <param name="renderList">经过剔除的节点索引集合</param>
        /// <param name="activeNodeList">要更新的节点集合</param>
        /// <param name="cullInfo">剔除信息</param>
        /// <param name="currentStat">当前节点统计信息</param>
        public static void DrawPhaseCulling(IndexList renderList, List<SceneNode> activeNodeList, CullingInfo cullInfo, ref RenderStatistics currentStat)
        {
            //将renderList归零
            renderList.Reset();

            //遍历activeNodeList生成要绘制的节点集合
            for (int i = 0; i < activeNodeList.Count; i++)
            {
                // 2D对象不需要进行剔除
                if (activeNodeList[i] is Renderable2DSceneNode)
                    continue;
                
                Renderable3DSceneNode node = activeNodeList[i] as Renderable3DSceneNode;
                if (node == null)
                    continue;

                Renderable3DSceneNode snode = (Renderable3DSceneNode)node;

                // 如果节点位标志中的Visible设为1，即节点可见
                if ((snode.frameBits & StatusBits.Visible) == StatusBits.Visible)
                {                    
                    // 则更新相应的统计信息
                    currentStat.nodesVisible++;

                    //--------------------------------------------------------------
                    //  如果关闭了剔除操作或这个节点不需要剔除
                    //--------------------------------------------------------------
                    if (cullInfo.CullingMethod == CullingMethod.NoCulling || (snode.frameBits & StatusBits.DisableCulling) == StatusBits.DisableCulling)
                    {
                        //  则将DrawPhaseCulled位设置为0
                        snode.frameBits &= ~StatusBits.DrawPhaseCulled;
                        currentStat.nodesCullingDisabled++;
                        renderList.AddIndex(i);
                        currentStat.nodesRendered++;

                        continue;
                    }

                    //--------------------------------------------------------------
                    //  检查Update过程是否计算了剔除
                    //--------------------------------------------------------------                  
                    if (((byte)snode.frameBits & (byte)StatusBits.UpdatePhaseFrustumChecked) == (byte)StatusBits.UpdatePhaseFrustumChecked)
                    {
                        //--------------------------------------------------------------
                        //  检查Update过程中是否已经将视锥体之外的节点剔除
                        //--------------------------------------------------------------
                        if (((byte)snode.frameBits & (byte)StatusBits.UpdatePhaseFrustumDisjoint) == (byte)StatusBits.UpdatePhaseFrustumDisjoint)
                        {
                            //  如果是，则将DrawPhaseCulled位设置为1
                            snode.frameBits |= StatusBits.DrawPhaseCulled;                               
                            currentStat.nodesBoundingBoxCulled++;
                            continue;
                        }
                    }
                    else
                    {
                        //--------------------------------------------------------------
                        //  如果不是，则我们必须计算视锥体剔除
                        //--------------------------------------------------------------                                    
                        if (cullInfo.BoundingFrustum.Contains(node.Pose.aaBB) == ContainmentType.Disjoint)
                        {
                            //  将DrawPhaseCulled位设置为1
                            snode.frameBits |= StatusBits.DrawPhaseCulled;                              
                            currentStat.nodesBoundingBoxCulled++;
                            continue;
                        }
                    }

                    //--------------------------------------------------------------
                    //  如果节点在真实相机视锥体中!
                    //  检查它是否在远裁平面之内。
                    //--------------------------------------------------------------   
                    if (cullInfo.CullingMethod == CullingMethod.FarPlaneFrustum)
                    {
                        //计算节点离开相机的距离
                        float distance = (node.Pose.Position - cullInfo.CameraPosition).Length() - node.Pose.Radius;
                        //如果此距离大于远裁平面乘以因子，说明节点在远裁平面之外需要剔除
                        if (distance > (cullInfo.FarPlane * node.FarPlaneCullingFactor))
                        {
                            //  将DrawPhaseCulled设置为1
                            snode.frameBits |= StatusBits.DrawPhaseCulled;                              
                            currentStat.nodesDistanceCulled++;
                            continue;
                        }
                    }

                    //  如果不属于前面的情况，则直接将节点添加到集合中 
                    renderList.AddIndex(i);
                    currentStat.nodesRendered++;
                    
                    //  将DrawPhaseCulled设置为0
                    snode.frameBits &= ~StatusBits.DrawPhaseCulled;
                }
            }
        }      
    }
}
